/*
 *  Filename: binShell
 *  Function: My shell program
 *  Created: 2017-04-07
 *  Version: 1.0
 *  Author: Bin
 */

/*-------- Includes --------*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <string.h>
#include "queue.h"

/*-------- Private defines --------*/
#define PARAMETER_SIZE 8
#define BUFFER_SIZE 80

/*-------- Private types --------*/
typedef struct {
    enum {
        NOREDIR, REDIRIN, REDIROUT
    } redirect;
    char *param[PARAMETER_SIZE];
    char *filename;
} cmdStruct;

/*-------- Function prototypes --------*/
char *strSplit(char *str, char ch);
void paramParse(char *str, char **param);
void cmdParse(char *str, cmdStruct *cmd);
void cmdExec(cmdStruct *cmd);
int cmdExecAll(char *cmdStr, int *pipe_a);

/*-------- Main functions --------*/
int main(int argc, char const *argv[])
{
    int ch, i;
    char *p;
    char buffer[BUFFER_SIZE];

    Queue_Init();

    while (1) {
        // Print command prompt and reset buffer
        printf("BinSh> ");
        memset(buffer, 0, BUFFER_SIZE);
        p = buffer;

        // Get input string
        for (i = 0; (ch = getchar()) != '\n' && i < BUFFER_SIZE; i++)
            buffer[i] = ch;
        buffer[i] = '\0';

        // Split command line
        while ((p = strSplit(p, '|')) != NULL)
            enQueue(p);

        // Execute command group
        if (cmdExecAll(buffer, NULL))
            break;
    }

    return 0;
}

/*-------- Private functions --------*/
char *strSplit(char *str, char ch)
{
    char *p = str;

    while (*p != ch && *p != '\0')
        p++;

    if (*p == ch) {
        *p = '\0';
        return ++p;
    } else {
        return NULL;
    }
}

void paramParse(char *str, char **param)
{
    int i = 0;
    char *p;

    param[i++] = strtok(str, " ");
    while ((p = strtok(NULL, " ")) != NULL && i < (PARAMETER_SIZE - 1))
        param[i++] = p;
    param[i] = NULL;
}

void cmdParse(char *str, cmdStruct *cmd)
{
    char *p;

    if ((p = strSplit(str, '<')) != NULL) {
        cmd->redirect = REDIRIN;
        paramParse(str, cmd->param);
        cmd->filename = strtok(p, " ");
    } else if ((p = strSplit(str, '>')) != NULL) {
        cmd->redirect = REDIROUT;
        paramParse(str, cmd->param);
        cmd->filename = strtok(p, " ");
    } else {
        cmd->redirect = NOREDIR;
        paramParse(str, cmd->param);
        cmd->filename = NULL;
    }
}

void cmdExec(cmdStruct *cmd)
{
    int fd;

    switch (cmd->redirect) {
    case REDIRIN:
        if ((fd = open(cmd->filename, O_RDONLY | O_CREAT, 0644)) < 0) {
            perror("redirect stdin");
            exit(1);
        }
        dup2(fd, STDIN_FILENO);
        close(fd);
        break;
    case REDIROUT:
        if ((fd = open(cmd->filename, O_WRONLY | O_CREAT | O_TRUNC, 0644)) < 0) {
            perror("redirect stdout");
            exit(1);
        }
        dup2(fd, STDOUT_FILENO);
        close(fd);
        break;
    case NOREDIR:
    default:
        break;
    }
    execvp(cmd->param[0], cmd->param);
    perror("exec");
    exit(1);
}

int cmdExecAll(char *cmdStr, int *pipe_a)
{
    char *nextCmdstr;
    int pid, newpipe_a[2];
    cmdStruct command;

    // pipe behind this command
    if ((nextCmdstr = deQueue()) != NULL) {
        if (pipe(newpipe_a) < 0) {
            perror("pipe");
            exit(1);
        }
    }

    // Check if command "exit"
    cmdParse(cmdStr, &command);
    if (strcmp("exit", command.param[0]) == 0)
        return 1;

    // Fork point
    if ((pid = fork()) < 0) {
        perror("fork");
        exit(1);
    }

    if (pid > 0) {      // parent
        // Close pipe in front of this command
        if (pipe_a != NULL) {
            close(pipe_a[0]);
            close(pipe_a[1]);
        }

        wait(NULL);

        // if exist next command group, than execute it
        if (nextCmdstr != NULL) {
            cmdExecAll(nextCmdstr, newpipe_a);
        }
    } else {            // child
        // pipe in front of this command
        if (pipe_a != NULL) {
            close(pipe_a[1]);
            dup2(pipe_a[0], STDIN_FILENO);
            close(pipe_a[0]);
        }

        // pipe behind this command
        if (nextCmdstr != NULL) {
            close(newpipe_a[0]);
            dup2(newpipe_a[1], STDOUT_FILENO);
            close(newpipe_a[1]);
        }

        // Execute this command
        cmdExec(&command);
    }

    return 0;
}
